package cn.zswltech.gruul.biz.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.ObjectUtil;
import cn.zswltech.gruul.biz.service.OrgService;
import cn.zswltech.gruul.biz.service.RedisService;
import cn.zswltech.gruul.biz.service.RoleService;
import cn.zswltech.gruul.biz.support.impl.IServiceImpl;
import cn.zswltech.gruul.common.constant.CustomTreeConstants;
import cn.zswltech.gruul.common.constant.MenuConstants;
import cn.zswltech.gruul.common.constant.OrgConstants;
import cn.zswltech.gruul.common.constant.RedisKeyConstant;
import cn.zswltech.gruul.common.dal.dao.*;
import cn.zswltech.gruul.common.entity.*;
import cn.zswltech.gruul.common.enums.OrgTypeEnum;
import cn.zswltech.gruul.common.enums.StatusCodeEnum;
import cn.zswltech.gruul.common.result.MSG;
import cn.zswltech.gruul.common.result.Response;
import cn.zswltech.gruul.exception.SelectionSystemException;
import cn.zswltech.gruul.web.api.util.AccountUtil;
import cn.zswltech.gruul.constants.RedisCacheKey;
import cn.zswltech.gruul.common.dal.vo.AccountVO;
import cn.zswltech.gruul.common.dal.vo.OrgRoleVO;
import cn.zswltech.gruul.web.api.util.RequestUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.util.Sqls;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service("orgService")
@Slf4j
public class OrgServiceImpl extends IServiceImpl<OrgDO> implements OrgService {
    @Autowired
    private OrgMenuFunctionDOMapper orgMenuFunctionDOMapper;

    @Resource(name = "roleService")
    private RoleService roleService;

    @Autowired
    private OrgDOMapper orgDOMapper;

    @Autowired
    private RoleDOMapper roleDOMapper;

    @Resource
    private UserDOMapper userDOMapper;

    @Resource
    private UserOrgRoleDOMapper userOrgRoleDOMapper;
    @Autowired
    private RedissonClient redissonClient;

    // 用于缓存机构信息，减少大量数据的数据库查询
    @Resource
    private RedisService redisService;

    @PostConstruct
    public void init() {
        refreshOrgCache();
    }

    @Override
    public void refreshOrgCache() {
        // 加载数据库机构信息到redis，并通过xxl-job定时刷新， 用于用户登录时的机构树查询、构建
        List<OrgDO> allOrgDOs = orgDOMapper.selectAll();
        redisService.set(RedisCacheKey.ALL_ORG_OD, JSON.toJSONString(allOrgDOs), 10 * 60);
    }


    @Override
    public Response<List<DisplayGroupDO>> getMenuFunctions(Long orgId) {
        Response<List<DisplayGroupDO>> response = new Response<List<DisplayGroupDO>>();
        if (orgId == null) {
            response.setCode(StatusCodeEnum.BUSSINESS_ERROR.getStatus());
            response.setMessage(MSG.req_error_param_null, "orgId");
            return response;
        }
        OrgDO queryIndb = orgDOMapper.queryByPrimaryKey(orgId);
        if (queryIndb == null) {
            log.error("机构:" + orgId + "不存在!");
            response.setCode(StatusCodeEnum.BUSSINESS_ERROR.getStatus());
            response.setMessage(MSG.req_error_org_not_exist, orgId.toString());
            return response;
        }
        OrgMenuFunctionDO queryMfDO = new OrgMenuFunctionDO();
        queryMfDO.setOrgId(orgId);
        //获取本级机构拥有的菜单
        Set<Long> menus = new HashSet<>();
        //获取角色拥有的功能
        Set<Long> funcs = new HashSet<>();
        try {
            List<OrgMenuFunctionDO> orgMenuFunctionDOs = orgMenuFunctionDOMapper.select(queryMfDO);
            for (OrgMenuFunctionDO omf : orgMenuFunctionDOs) {
                menus.add(omf.getMenuId());
                funcs.add(omf.getFunctionId());
            }
            boolean isTopLevel = false;
            if (queryIndb.getLevel() == OrgConstants.LEVEL_1) {
                isTopLevel = true;
            } else {
                //如果是2、3级查看上级存量
                orgId = queryIndb.getParentId();
            }
            List<DisplayGroupDO> respList = roleService.parseOrgMenuTree(orgId, new ArrayList<>(menus), new ArrayList<>(funcs), isTopLevel);
            return Response.success(respList);
        } catch (Exception e) {
            log.error("parse org menu function tree Error:", e);
            response.setCode(StatusCodeEnum.DATABASE_ERROR.getStatus());
            response.setMsg(StatusCodeEnum.DATABASE_ERROR.getMsg());
        }
        return response;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response setMenuFunctions(Long orgId, String params) {
        Response response = new Response();
        if (orgId == null) {
            response.setMessage(MSG.req_error_param_null, "orgId");
            return response;
        }
        if (StringUtils.isEmpty(params)) {
            response.setMessage(MSG.req_error_param_null, "params");
            return response;
        }
        try {
            JSONObject jsonObject = JSON.parseObject(params);
            if (jsonObject == null) {
                response.setMsg("params");
                return response;
            }
            Date time = new Date();
            // 处理新增列表
            JSONArray addArray = jsonObject.getJSONArray("add");
            if (addArray != null) {
                int size = addArray.size();
                List<OrgMenuFunctionDO> insertList = Lists.newArrayList();
                for (int i = 0; i < size; i++) {
                    JSONObject paramObj = addArray.getJSONObject(i);
                    OrgMenuFunctionDO orgMenuFunctionDO = new OrgMenuFunctionDO();
                    orgMenuFunctionDO.setGmtCreate(time);
                    orgMenuFunctionDO.setGmtModified(time);
                    orgMenuFunctionDO.setOrgId(orgId);
                    orgMenuFunctionDO.setCustomTreeId(Long.valueOf(paramObj.getString("customTreeId")));
                    if (paramObj.getString("menuId") == null) {
                        continue;
                    }
                    orgMenuFunctionDO.setMenuId(Long.valueOf(paramObj.getString("menuId")));
                    if (CustomTreeConstants.OPT_TYPE_FUNCTION.equals(paramObj.getString("optType"))) {
                        orgMenuFunctionDO.setFunctionId(Long.valueOf(paramObj.getString("functionId")));
                    }
                    insertList.add(orgMenuFunctionDO);
                }
                if (CollectionUtils.isNotEmpty(insertList)) {
                    orgMenuFunctionDOMapper.insertList(insertList);
                }
            }
            // 处理删除列表
            JSONArray delArray = jsonObject.getJSONArray("delete");
            if (delArray != null) {
                int size = delArray.size();
                for (int i = 0; i < size; i++) {
                    // 删菜单\功能
                    JSONObject paramObj = delArray.getJSONObject(i);
                    Long menuId = paramObj.getLong("menuId");
                    String optType = paramObj.getString("optType");
                    Long functionId = paramObj.getLong("functionId");

                    //递归删除
                    deleteFromEnd(orgId, menuId, optType, functionId);
                }
            }
            return Response.success(null);
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            log.error("API set org menu function error", e);
            response.setMessage(MSG.req_error_rel_menu_funcation_failed);
        }
        return response;
    }

    @Override
    public List<OrgDO> selectByIds(List<Long> deptIdList, int type) {
        if (CollectionUtils.isEmpty(deptIdList)) {
            return new ArrayList<>();
        }
        return orgDOMapper.selectByIds(deptIdList, type);
    }

    @Override
    public List<OrgDO> queryByParentIds(Collection<Long> parentIds) {
        if (CollectionUtils.isEmpty(parentIds)) {
            return Collections.emptyList();
        }
        Example example = new Example(OrgDO.class);
        example.createCriteria().andIn("parentId", parentIds);
        return orgDOMapper.selectByExample(example);
    }

    @Override
    public void pushOrg2Redis() {
        List<OrgDO> orgDOS = orgDOMapper.selectAll();
        if (ObjectUtil.isEmpty(orgDOS)) {
            return;
        }
        Map<String, String> orgModifyMap = orgDOS.stream().collect(Collectors.toMap(OrgDO::getCode, item -> DateUtil.format(item.getGmtModified(), "yyyy-MM-dd")));
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        RMap<Object, Object> rMap = redissonClient.getMap(RedisKeyConstant.YUNHU_ORG_MODIFY_DATE_KEY);
        rMap.putAll(orgModifyMap);
        log.info("推送机构数据到redis完成");
        stopWatch.stop();
        stopWatch.prettyPrint(TimeUnit.MILLISECONDS);
    }

    private void deleteFromEnd(Long orgId, Long menuId, String optType, Long functionId) throws Exception {
        List<OrgDO> orgDOs = orgDOMapper.queryByParentId(orgId);
        for (OrgDO orgDO : orgDOs) {
            deleteFromEnd(orgDO.getId(), menuId, optType, functionId);
        }
        deleteOrgMenuFunctions(orgId, menuId, optType, functionId);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteOrgMenuFunctions(Long orgId, Long menuId, String optType, Long functionId) throws Exception {
        if (orgId == null && menuId == null) {
            return;
        }
        OrgMenuFunctionDO orgMenuFunctionDO = new OrgMenuFunctionDO();
        orgMenuFunctionDO.setOrgId(orgId);
        orgMenuFunctionDO.setMenuId(menuId);
        if (CustomTreeConstants.OPT_TYPE_FUNCTION.equals(optType)) {  // 删功能
            orgMenuFunctionDO.setFunctionId(functionId);
        }
        int count = orgMenuFunctionDOMapper.deleteByMenuFunction(orgMenuFunctionDO);
        log.info("deleteOrgMenuFunctions count:" + count);
        // 删除机构下的角色关联菜单\功能
        // 查出机构下关联的角色列表
        RoleDO queryRole = new RoleDO();
        queryRole.setOrgId(orgId);
        List<RoleDO> roles = roleDOMapper.select(queryRole);
        if (CollectionUtils.isNotEmpty(roles)) {
            for (RoleDO roleDO : roles) {
                roleService.deleteRoleMenuFunctions(roleDO.getId(), menuId, optType, functionId);
            }
        }
    }

    @Override
    public Response<List<OrgDO>> getOrgList() {
        return getOrgList(false, false);
    }

    @Override
    public Response<List<OrgDO>> getOrgList(Boolean orgOnly, Boolean orgSelect) {
        //获取用户选择的系统
        String systemCode = RequestUtil.getSystemCode();
        //为null,返回错误信息
        if (StringUtils.isEmpty(systemCode)) {
            log.error(MSG.req_error_unknown_system_code.getMsg());
            return Response.error(MSG.req_error_unknown_system_code);
        }

        List<OrgDO> orgDOs = orgDOMapper.queryBysSystemCode(systemCode);

        if (CollectionUtils.isEmpty(orgDOs)) {
            return Response.success(orgDOs);
        }
        // 找到用户所在机构的跟节点
        if (Objects.nonNull(orgSelect) && orgSelect) {
            List<OrgDO> userOrgs = getUserOrgList();
            if (CollectionUtils.isNotEmpty(userOrgs)) {
                orgDOs = userOrgs;
            }
        }

        // 排除多个根节点有同一父级节点的情况
        Integer minLevel = orgDOs.stream().map(OrgDO::getLevel).min(Comparator.comparing(Integer::intValue)).get();
        // level 为必填项，不判空
        // 最小层级的机构Id set
        Set<Long> minLevelOrgs = orgDOs.stream().filter(t -> t.getLevel().equals(minLevel)).map(OrgDO::getId).collect(Collectors.toSet());
        Iterator<OrgDO> iterator = orgDOs.iterator();

        while (iterator.hasNext()) {
            OrgDO orgDO = iterator.next();
            if (orgDO.getLevel().equals(minLevel)) {
                continue;
            }
            int level = orgDO.getLevel();
            while (level - minLevel > 0) {
                // 一个用户有多个不同公司是少数情况，且公司之前层级差距不会太大
                OrgDO father = orgDOMapper.selectByPrimaryKey(orgDO.getParentId());
                if (minLevelOrgs.contains(father.getId())) {
                    iterator.remove();
                    break;
                }
                level--;
            }
        }
        buildOrgStructure(orgDOs, orgOnly);
        return Response.success(orgDOs);
    }

    private void buildOrgStructure(List<OrgDO> orgDOList, Boolean orgOnly) {
        StopWatch stw = new StopWatch("buildOrgStructure计时器");

        List<OrgDO> orgDOS;
        // 先尝试从redis获取
        stw.start("redis读取全量org数据对象");
        orgDOS = this.getAndFilterOrgDOsFromCache(orgOnly);
        stw.stop();
        // redis没有获取到缓存，查数据库
        if (orgDOS == null) {
            Example example = new Example(OrgDO.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andIsNotNull("parentId").andIsNotNull("type");
            // 不查部门的情况
            if (Objects.nonNull(orgOnly) && orgOnly) {
                criteria.andNotEqualTo("type", OrgTypeEnum.DEP.getCode());
            }
            stw.start("查询所有parentId非空的机构");
            orgDOS = orgDOMapper.selectByExample(example); // 这里机构数量较多，速度较慢
            stw.stop();
        }


        stw.start("根据parentId对机构对象进行分组");
        Map<Long, List<OrgDO>> pidMap = orgDOS.stream().collect(Collectors.groupingBy(OrgDO::getParentId));
        stw.stop();
        Queue<OrgDO> queue = Lists.newLinkedList();
        // 先加入所有一级机构，由上而下填充下级机构，组成树
        queue.addAll(orgDOList);

        stw.start("遍历所有机构，填充子节点");
        while (!queue.isEmpty()) {
            OrgDO poll = queue.poll();
            List<OrgDO> childrenCollection = pidMap.get(poll.getId());
            if (!CollectionUtils.isEmpty(childrenCollection)) {
                if (Objects.nonNull(orgOnly) && orgOnly) {
                    childrenCollection.removeIf(t -> Objects.isNull(t.getType()) || t.getType().equals(OrgTypeEnum.DEP.getCode()));
                }
                // 排序
                childrenCollection.sort(Comparator.comparing((OrgDO t) -> t.getType().equals(OrgTypeEnum.DEP.getCode()) ? 0 : 1)
                        .thenComparing(OrgDO::getId));
                poll.setChildren(childrenCollection);
                queue.addAll(childrenCollection);
            }
        }
        stw.stop();
        log.info("buildOrgStructure耗时情况:{}", stw.prettyPrint(TimeUnit.MILLISECONDS));
    }

    private List<OrgDO> getAndFilterOrgDOsFromCache(Boolean orgOnly) {
        String value = redisService.get(RedisCacheKey.ALL_ORG_OD);
        if (StringUtils.isNotBlank(value)) {
            try {
                List<OrgDO> orgDOS = JSON.parseObject(value, new TypeReference<List<OrgDO>>() {
                });
                orgDOS.removeIf(e -> e.getParentId() == null || e.getType() == null);
                if (orgOnly != null && orgOnly) {
                    orgDOS.removeIf(e -> OrgTypeEnum.DEP.getCode().equals(e.getType()));
                }
                return orgDOS;
            } catch (Exception e) {
                log.error("redis缓存解析失败, key = {}", RedisCacheKey.ALL_ORG_OD, e);
            }
        }
        return null;
    }

    public List<OrgDO> getUserOrgList() {
        List<OrgDO> res = Lists.newArrayList();
        AccountVO accountVO = AccountUtil.getLoginInfo();
        if (accountVO == null) {
            return null;
        }
        UserDO userDO = userDOMapper.selectByPrimaryKey(accountVO.getId());
        if (userDO == null) {
            return null;
        }
        List<OrgRoleVO> orgRoleVOS = userOrgRoleDOMapper.selectOrgAndRoleInfo(Collections.singletonList(userDO.getId()));
        List<Long> orgIds = orgRoleVOS.stream().map(OrgRoleVO::getOrgId).distinct().collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(orgIds)) {
            Example example = new Example(OrgDO.class);
            example.createCriteria().andIn("id", orgIds);
            List<OrgDO> orgDOS = orgDOMapper.selectByExample(example);
            for (OrgDO orgDO : orgDOS) {
                OrgDO rootOrg = getRootOrg(orgDO);
                if (Objects.nonNull(rootOrg)) {
                    res.add(rootOrg);
                }
            }
        }
        return res;
    }

    private OrgDO getRootOrg(OrgDO orgDO) {
        // 不是注册单位(公司)且还有上级，就继续往上找
        OrgDO res = orgDO;
        while (res.getParentId() != null && !OrgTypeEnum.REGISTER_UNIT.getCode().equals(res.getType())) {
            res = orgDOMapper.selectByPrimaryKey(res.getParentId());
        }
        return res;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response deleteOrg(Long id) {
        Response response = new Response();
        if (id == null) {
            response.setMessage(MSG.req_error_param_null, "id");
            return response;
        }
        try {
            OrgDO orgIndb = orgDOMapper.queryByPrimaryKey(id);
            if (orgIndb == null) {
                response.setMessage(MSG.req_error_param_invalid, "id");
                return response;
            }

            // 检查是否有子机构
            List<OrgDO> orgDOs = orgDOMapper.queryByParentId(id);
            if (CollectionUtils.isNotEmpty(orgDOs)) {
                response.setMessage(MSG.req_error_delete_sub_org);
                return response;
            }
            // 检查是否有角色
            RoleDO roleQuery = new RoleDO();
            roleQuery.setOrgId(id);
            List<RoleDO> roleDOs = roleDOMapper.select(roleQuery);
            if (CollectionUtils.isNotEmpty(roleDOs)) {
                response.setMessage(MSG.req_err_delete_role);
                return response;
            }
            // 删除机构
            deleteByPrimaryKey(id);
            this.deleteOrgMenuFunctions(id, null, null, null);
            return Response.success(null);
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            log.error("API delete org error", e);
            response.setMessage(MSG.req_error_delete_failed);
        }
        return response;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response updateOrg(OrgDO orgDO) {
        Response response = new Response();
        //校验参数
        if (orgDO.getId() == null) {
            response.setMessage(MSG.req_error_param_null, "id");
            return response;
        }
        OrgDO orgIndb = orgDOMapper.queryByPrimaryKey(orgDO.getId());
        if (orgIndb == null) {
            response.setMessage(MSG.req_error_param_invalid, "id");
            return response;
        }
        if (StringUtils.isBlank(orgDO.getCode())) {
            response.setMessage(MSG.req_error_param_null, "code");
            return response;
        }
        OrgDO org;
        try {
            Example example = Example.builder(OrgDO.class).where(Sqls.custom().andEqualTo("code", orgDO.getCode())).build();
            org = orgDOMapper.selectOneByExample(example);
        } catch (Exception e) {
            e.printStackTrace();
            return Response.error(MSG.req_error_param_code_repeat);
        }
        if (!(org != null && org.getId().equals(orgDO.getId()))) {
            return Response.error(MSG.req_error_param_code_repeat);
        }
        if (orgDO.getName() != null && StringUtils.isBlank(orgDO.getName())) {
            response.setMessage(MSG.req_error_param_null, "name");
            return response;
        }
        if (orgDO.getName() != null && orgDO.getName().length() > 80) {
            response.setMessage(MSG.req_error_no_than_length, "80");
            return response;
        }
        if (orgDO.getCode() != null && orgDO.getCode().length() > 128) {
            response.setMessage(MSG.req_error_no_than_length, "128");
            return response;
        }
        BeanUtils.copyProperties(orgDO, orgIndb);
        orgDO.setGmtModified(new Date());

        try {
            orgDOMapper.updateByPrimaryKeySelective(orgIndb);
            return Response.success(null);
        } catch (Exception e) {
            log.error("API update org error", e);
            response.setMessage(MSG.req_error_modify_failed);
        }
        return response;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response<Map> createOrg(OrgDO orgDO) {
        Response response = new Response();
        if (checkParams(orgDO, response)) {
            return response;
        }
        if (orgDO.getLevel() == null) {
            response.setMessage(MSG.req_error_param_null, "level");
            return response;
        }
        if (orgDO.getLevel() < MenuConstants.LEVEL_1) {
            response.setMessage(MSG.req_error_param_tip, "level", "[1,2,....]");
            return response;
        }
        // 除了1级机构,其他目录parentId必须要传
        if (orgDO.getLevel() == OrgConstants.LEVEL_1) {
            orgDO.setParentId(null);
        } else {
            if (orgDO.getParentId() == null) {
                response.setMessage(MSG.req_error_param_null, "parentId");
                return response;
            }
            OrgDO orgIndb = orgDOMapper.queryByPrimaryKey(orgDO.getParentId());
            if (orgIndb == null || orgIndb.getLevel() != orgDO.getLevel() - 1) {
                response.setMessage(MSG.req_error_param_invalid, "parentId");
                return response;
            }
        }
        try {
            mapper.insert(orgDO);
            return Response.success(genParamsMap("id", orgDO.getId()));
        } catch (Exception e) {
            log.error("API create org error", e);
            if (e.getMessage().contains("uk_")) {
                return Response.error(MSG.req_error_param_code_repeat);
            }
            response.setMessage(MSG.req_error_create_failed);
        }
        return response;
    }

    private boolean checkParams(OrgDO orgDO, Response response) {
        if (StringUtils.isBlank(orgDO.getName())) {
            response.setMessage(MSG.req_error_param_null, "name");
            return true;
        }
        if (StringUtils.isBlank(orgDO.getCode())) {
            response.setMessage(MSG.req_error_param_null, "code");
            return true;
        }

        if (orgDO.getName().length() > 80) {
            response.setMessage(MSG.req_error_no_than_length, "80");
            return true;
        }

        if (orgDO.getCode().length() > 128) {
            response.setMessage(MSG.req_error_no_than_length, "128");
            return true;
        }

        return false;
    }
}
